﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//


/********************************************************
 * ADO.NET 2.0 Data Provider for SQLLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 ********************************************************/

using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.ComponentModel;
using EffiProz.Core;

namespace System.Data.EffiProz
{

#if !PLATFORM_COMPACTFRAMEWORK
    [Designer("EffiProz.Designer.EfzCommandDesigner, EffiProz.Designer, Version=0.0.1.0, Culture=neutral, PublicKeyToken=89e74d577370ac3d"), ToolboxItem(true)]
#endif
    public sealed class EfzCommand : DbCommand, ICloneable
    {
        private CommandType _commandType;
        /// <summary>
        /// The command text this command is based on
        /// </summary>
        private string _commandText;
        /// <summary>
        /// The connection the command is associated with
        /// </summary>
        private EfzConnection _cnn;
        /// <summary>
        /// The version of the connection the command is associated with
        /// </summary>
        private long _version;
        /// <summary>
        /// Indicates whether or not a DataReader is active on the command.
        /// </summary>
        private WeakReference _activeReader;
        /// <summary>
        /// The timeout for the command, kludged because EffiProz doesn't support per-command timeout values
        /// </summary>
        internal int _commandTimeout;
        /// <summary>
        /// Designer support
        /// </summary>
        private bool _designTimeVisible;
        /// <summary>
        /// Used by DbDataAdapter to determine updating behavior
        /// </summary>
        private UpdateRowSource _updateRowSource;
        /// <summary>
        /// The collection of parameters for the command
        /// </summary>
        private EfzParameterCollection _parameterCollection;


        internal EfzStatement _statement;

        /// <summary>
        /// Transaction associated with this command
        /// </summary>
        private EfzTransaction _transaction;

       
        /** The maximum number of rows to generate when executing this statement. */
        public int MaxRows
        {
            get;
            set;
        }

        public int FetchSize
        {
            get;
            set;
        }

        public bool FetchGeneratedResults
        {
            get;
            set;
        }

        ///<overloads>
        /// Constructs a new EffiProzCommand
        /// </overloads>
        /// <summary>
        /// Default constructor
        /// </summary>
        public EfzCommand()
            : this(null, null,null)
        {
        }

        /// <summary>
        /// Initializes the command with the given command text
        /// </summary>
        /// <param name="commandText">The SQL command text</param>
        public EfzCommand(string commandText)
            : this(commandText, null, null)
        {
        }

        /// <summary>
        /// Initializes the command with the given SQL command text and attach the command to the specified
        /// connection.
        /// </summary>
        /// <param name="commandText">The SQL command text</param>
        /// <param name="connection">The connection to associate with the command</param>
        public EfzCommand(string commandText, EfzConnection connection)
            : this(commandText, connection, null)
        {
        }

        /// <summary>
        /// Initializes the command and associates it with the specified connection.
        /// </summary>
        /// <param name="connection">The connection to associate with the command</param>
        public EfzCommand(EfzConnection connection)
            : this(null, connection, null)
        {
        }

        private EfzCommand(EfzCommand source)
            : this(source.CommandText, source.Connection, source.Transaction)
        {
            CommandTimeout = source.CommandTimeout;
            _commandType = CommandType.Text;
            DesignTimeVisible = source.DesignTimeVisible;
            UpdatedRowSource = source.UpdatedRowSource;

            foreach (EfzParameter param in source._parameterCollection)
            {
                Parameters.Add(param.Clone());
            }
        }

        /// <summary>
        /// Initializes a command with the given SQL, connection and transaction
        /// </summary>
        /// <param name="commandText">The SQL command text</param>
        /// <param name="connection">The connection to associate with the command</param>
        /// <param name="transaction">The transaction the command should be associated with</param>
        public EfzCommand(string commandText, EfzConnection connection, EfzTransaction transaction)
        {
            _commandTimeout = 30;
            _commandType = CommandType.Text;
            _parameterCollection = new EfzParameterCollection(this);
            _designTimeVisible = true;
            _updateRowSource = UpdateRowSource.None;

            if (commandText != null)
                CommandText = commandText;

            if (connection != null)
            {
                DbConnection = connection;
               // _commandTimeout = connection.connectio;
            }

            if (transaction != null)
                Transaction = transaction;                     

            FetchGeneratedResults = false;
        }

        /// <summary>
        /// Disposes of the command and clears all member variables
        /// </summary>
        /// <param name="disposing">Whether or not the class is being explicitly or implicitly disposed</param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                // If a reader is active on this command, don't destroy the command, instead let the reader do it
                EfzDataReader reader = null;
                if (_activeReader != null)
                {
                    try
                    {
                        reader = _activeReader.Target as EfzDataReader;
                    }
                    catch (InvalidOperationException)
                    {
                    }
                }

                if (reader != null)
                {
                    reader._disposeCommand = true;
                    _activeReader = null;
                    return;
                }

                Connection = null;
                _parameterCollection.Clear();
                _commandText = null;
            }
        }

        /// <summary>
        /// Clears and destroys all statements currently prepared
        /// </summary>
        internal void ClearCommands()
        {
            if (_activeReader != null)
            {
                EfzDataReader reader = null;
                try
                {
                    reader = _activeReader.Target as EfzDataReader;
                }
                catch (InvalidOperationException)
                {
                }

                if (reader != null)
                    reader.Close();

                _activeReader = null;
            }

            if (_statement != null)
                _statement.Dispose();

            _statement = null;
            _parameterCollection.Unbind();
        }





        /// <summary>
        /// Not implemented
        /// </summary>
        public override void Cancel()
        {
            if (_activeReader != null)
            {
                EfzDataReader reader = _activeReader.Target as EfzDataReader;
                if (reader != null)
                    reader.Cancel();
            }
        }

       
        /// <summary>
        /// The SQL command text associated with the command
        /// </summary>
    #if !PLATFORM_COMPACTFRAMEWORK
    [DefaultValue(""), RefreshProperties(RefreshProperties.All), Editor("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
    #endif
        public override string CommandText
        {
            get
            {
                return _commandText;
            }
            set
            {
                if (_commandText == value) return;

                if (_activeReader != null && _activeReader.IsAlive)
                {
                    throw new InvalidOperationException("Cannot set CommandText while a DataReader is active");
                }

                ClearCommands();
                _commandText = value;
                _statement = null;
            }
        }

        /// <summary>
        /// The amount of time to wait for the connection to become available before erroring out
        /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
    [DefaultValue((int)30)]
#endif
        public override int CommandTimeout
        {
            get
            {
                return _commandTimeout;
            }
            set
            {
                _commandTimeout = value;
            }
        }

        /// <summary>
        /// The type of the command.  EffiProz only supports CommandType.Text
        /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
    [RefreshProperties(RefreshProperties.All), DefaultValue(CommandType.Text)]
#endif
        public override CommandType CommandType
        {
            get
            {
                return _commandType;
            }
            set
            {
                if (value != CommandType.Text && value != CommandType.StoredProcedure)
                    throw new NotSupportedException();
                _commandType = value;
            }
        }

        /// <summary>
        /// Forwards to the local CreateParameter() function
        /// </summary>
        /// <returns></returns>
        protected override DbParameter CreateDbParameter()
        {
            return CreateParameter();
        }

        /// <summary>
        /// Create a new parameter
        /// </summary>
        /// <returns></returns>
        public new EfzParameter CreateParameter()
        {
            return new EfzParameter();
        }

        /// <summary>
        /// The connection associated with this command
        /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
        [DefaultValue((string)null), Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
#endif
        public new EfzConnection Connection
        {
            get { return _cnn; }
            set
            {
                if (_activeReader != null && _activeReader.IsAlive)
                    throw new InvalidOperationException("Cannot set Connection while a DataReader is active");

                if (_cnn != null)
                {
                    ClearCommands();         
                }

                _cnn = value;
                if (_cnn != null)
                    _version = _cnn.Version;
              
            }
        }

        /// <summary>
        /// Forwards to the local Connection property
        /// </summary>
        protected override DbConnection DbConnection
        {
            get
            {
                return Connection;
            }
            set
            {
                Connection = (EfzConnection)value;
            }
        }

        /// <summary>
        /// Returns the EffiProzDBParameterCollection for the given command
        /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
#endif
        public new EfzParameterCollection Parameters
        {
            get { return _parameterCollection; }
        }

        /// <summary>
        /// Forwards to the local Parameters property
        /// </summary>
       
        protected override DbParameterCollection DbParameterCollection
        {
            get
            {
                return Parameters;
            }
        }

        /// <summary>
        /// The transaction associated with this command.  EffiProz only supports one transaction per connection, so this property forwards to the
        /// command's underlying connection.
        /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
        public new EfzTransaction Transaction
        {
            get { return _transaction; }
            set
            {
                if (_cnn != null)
                {
                    if (_activeReader != null && _activeReader.IsAlive)
                        throw new InvalidOperationException("Cannot set Transaction while a DataReader is active");

                    if (value != null)
                    {
                        if (value._cnn != _cnn)
                            throw new ArgumentException("Transaction is not associated with the command's connection");
                    }
                    _transaction = value;
                }
                else
                {
                    if (value != null) Connection = value.Connection;
                    _transaction = value;
                }
            }
        }

        /// <summary>
        /// Forwards to the local Transaction property
        /// </summary>
        protected override DbTransaction DbTransaction
        {
            get
            {
                return Transaction;
            }
            set
            {
                Transaction = (EfzTransaction)value;
            }
        }

        /// <summary>
        /// This function ensures there are no active readers, that we have a valid connection,
        /// that the connection is open, that all statements are prepared and all parameters are assigned
        /// in preparation for allocating a data reader.
        /// </summary>
        private void InitializeForReader()
        {
            if (_activeReader != null && _activeReader.IsAlive)
            {
                //throw new InvalidOperationException("DataReader already active on this command");
                ClearDataReader();
            }

            if (_cnn == null)
                throw new InvalidOperationException("No connection associated with this command");

            if (_cnn.State != ConnectionState.Open)
                throw new InvalidOperationException("Database is not open");

            // If the version of the connection has changed, clear out any previous commands before starting
            if (_cnn.Version != _version)
            {
                _version = _cnn.Version;
                ClearCommands();
            }           
          
        }

        /// <summary>
        /// Creates a new EffiProzDBDataReader to execute/iterate the array of EffiProz prepared statements
        /// </summary>
        /// <param name="behavior">The behavior the data reader should adopt</param>
        /// <returns>Returns a EffiProzDBDataReader object</returns>
        protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
        {
            return ExecuteReader(behavior);
        }

        /// <summary>
        /// Overrides the default behavior to return a EffiProzDBDataReader specialization class
        /// </summary>
        /// <param name="behavior">The flags to be associated with the reader</param>
        /// <returns>A EffiProzDBDataReader</returns>
        public new EfzDataReader ExecuteReader(CommandBehavior behavior)
        {
            InitializeForReader();

            EfzDataReader rd = new EfzDataReader(this, behavior);
            _activeReader = new WeakReference(rd, false);

            return rd;
        }



        /// <summary>
        /// Overrides the default behavior of DbDataReader to return a specialized EffiProzDBDataReader class
        /// </summary>
        /// <returns>A EffiProzDBDataReader</returns>
        public new EfzDataReader ExecuteReader()
        {
            return ExecuteReader(CommandBehavior.Default);
        }

        /// <summary>
        /// Called by the EffiProzDBDataReader when the data reader is closed.
        /// </summary>
        internal void ClearDataReader()
        {
            _activeReader = null;
        }

        /// <summary>
        /// Execute the command and return the number of rows inserted/updated affected by it.
        /// </summary>
        /// <returns></returns>
        public override int ExecuteNonQuery()
        {
            using (EfzDataReader reader = ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult))
            {
                while (reader.NextResult()) ;
                return reader.RecordsAffected;
            }
        }


        /// <summary>
        /// Execute the command and return the first column of the first row of the resultset
        /// (if present), or null if no resultset was returned.
        /// </summary>
        /// <returns>The first column of the first row of the first resultset from the query</returns>
        public override object ExecuteScalar()
        {
            InitializeForReader();

            using (EfzDataReader reader = ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult))
            {
                if (reader.Read())
                    return reader[0];
            }
            return null;
        }

     
       
        public override void Prepare() 
        {
            if (_statement != null)
                _statement.Dispose();

            _statement = new EfzStatement(this, _commandText, true);
        }

        /// <summary>
        /// Sets the method the EffiProzCommandBuilder uses to determine how to update inserted or updated rows in a DataTable.
        /// </summary>
        [DefaultValue(UpdateRowSource.None)]
        public override UpdateRowSource UpdatedRowSource
        {
            get
            {
                return _updateRowSource;
            }
            set
            {
                _updateRowSource = value;
            }
        }

        /// <summary>
        /// Determines if the command is visible at design time.  Defaults to True.
        /// </summary>
#if !PLATFORM_COMPACTFRAMEWORK
        [DesignOnly(true), Browsable(false), DefaultValue(true), EditorBrowsable(EditorBrowsableState.Never)]
#endif
        public override bool DesignTimeVisible
        {
            get
            {
                return _designTimeVisible;
            }
            set
            {
                _designTimeVisible = value;

            }
        }

        /// <summary>
        /// Clones a command, including all its parameters
        /// </summary>
        /// <returns>A new EffiProzCommand with the same commandtext, connection and parameters</returns>
        public object Clone()
        {
            return new EfzCommand(this);
        }        
      
    }

}
